package org.testng.junit; import com.sun.javadoc.ClassDoc; import com.sun.javadoc.Doclet; import com.sun.javadoc.MethodDoc; import com.sun.javadoc.RootDoc; import com.sun.javadoc.SourcePosition; import com.sun.tools.javadoc.Main; import org.testng.JUnitConverter; import org.testng.collections.Lists; import org.testng.collections.Maps; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; /** * This class converts a file to TestNG if it's a JUnit test class. * * @author <a href="mailto:cedric@beust.com">Cedric Beust</a> * @author <a href="mailto:the_mindstorm@evolva.ro">the_mindstorm</a> */ public class JUnitTestConverter extends Doclet { private static File[] m_fileNames = null; private static List<String> m_classNames = Lists.newArrayList(); // Files and their test methods private static Map<File, List<MethodDoc>> m_files = Maps.newHashMap(); private static Map<File, List<String>> m_fileLines = Maps.newHashMap(); private static Map<File, String> m_packageNames = Maps.newHashMap(); private static Map<File, Integer> m_typelines = Maps.newHashMap(); private File m_outDir = null; private boolean m_useAnnotations; private boolean m_done; private String[] m_groups; /** * @param fileNames */ public JUnitTestConverter(File[] fileNames, File outDir, String release, boolean useAnnotations, String[] groups) { m_fileNames = fileNames; m_outDir = outDir; m_useAnnotations = useAnnotations; m_groups = groups; Collection<String> argv = Lists.newArrayList(); argv.add("-quiet"); if (null != release && !"".equals(release)) { argv.add("-source"); argv.add(release); } argv.add("-doclet"); argv.add("org.testng.junit.JUnitTestConverter"); for (File fn : fileNames) { argv.add(fn.getAbsolutePath()); } String[] newArgv = argv.toArray(new String[argv.size()]); m_done = 0 == Main.execute(newArgv); } private static File findFileName(ClassDoc cd) { for (File fn : m_fileNames) { if (fn.getAbsolutePath().endsWith(cd.name() + ".java")) { return fn; } } assert false : "COULDN'T FIND FILE " + cd.name(); return null; } /** * This method is required for all doclets. * * @return true on success. */ public static boolean start(RootDoc root) { ClassDoc[] classes = root.classes(); for (ClassDoc cd : classes) { if (!isJUnitTest(cd)) { continue; } if(!cd.isAbstract()) m_classNames.add(cd.qualifiedTypeName()); File file; if (null != cd.position().file()) { file = cd.position().file(); } else { file = findFileName(cd); } String fqn = cd.qualifiedTypeName(); int tn = fqn.indexOf(cd.typeName()); if (tn > 0) { m_packageNames.put(file, fqn.substring(0, tn - 1)); } m_typelines.put(file, new Integer(cd.position().line())); MethodDoc[] methods = cd.methods(); List<MethodDoc> testMethods = Lists.newArrayList(); for (MethodDoc md : methods) { if (isTest(md) || isSetUp(md) || isTearDown(md)) { // Add the lines backward, so we can insert them without messing // up their order later testMethods.add(0, md); } } m_files.put(file, testMethods); } return true; } /** * Query the superclasses in order to find out if the current classdoc is from * a real <CODE>TestCase</CODE>. */ private static boolean isJUnitTest(ClassDoc clsDoc) { if (clsDoc.isInterface()) { return false; } ClassDoc superCls = clsDoc.superclass(); while (null != superCls && !"java.lang.Object".equals(superCls.qualifiedTypeName())) { if ("junit.framework.TestCase".equals(superCls.qualifiedTypeName()) || "TestCase".equals(superCls.typeName())) { return true; } superCls = superCls.superclass(); } return false; } private static boolean isTest(MethodDoc md) { return md.name().startsWith("test"); } private static boolean isSetUp(MethodDoc md) { return "setUp".equals(md.name()); } private static boolean isTearDown(MethodDoc md) { return "tearDown".equals(md.name()); } public int convert() { if (!m_done) { return -1; } int converted = 0; for (File file : m_files.keySet()) { try { List<String> lines = fileToLines(file); List<String> finalLines = m_useAnnotations ? insertAnnotations(m_files .get(file), lines) : insertJavadoc(file, m_files .get(file), lines); m_fileLines.put(file, finalLines); writeFile(file); converted++; } catch (IOException ioe) { ppp("failed to process " + file); ioe.printStackTrace(); } } return converted; } private void writeFile(File filePath) { String fileName = filePath.getName(); File file = new File(getPackageOutputDir(m_outDir, m_packageNames.get(filePath)), fileName); File parentDir = file.getParentFile(); parentDir.mkdirs(); FileWriter fw = null; BufferedWriter bw = null; try { fw = new FileWriter(file); bw = new BufferedWriter(fw); List<String> lines = m_fileLines.get(filePath); assert null != lines : "NO LINES FOR " + filePath; for (String l : lines) { bw.write(l); bw.write('\n'); } ppp("Wrote " + file.getAbsolutePath()); } catch (IOException e) { e.printStackTrace(); } finally { try { if (null != bw) { bw.close(); } } catch (IOException ioe) { } try { if (null != fw) { fw.close(); } } catch (IOException ioe) { } } } private List<String> insertAnnotations(List<MethodDoc> methodDocs, List<String> lines) { // // Add import // int lineCount = 0; for (String line : lines) { lineCount++; line = line.trim(); if(line.startsWith("import")) { lines.add(lineCount - 1, "import org.testng.annotations.Test;"); lines.add(lineCount - 1, "import org.testng.annotations.BeforeMethod;"); lines.add(lineCount - 1, "import org.testng.annotations.AfterMethod;"); break; } } String groupsLine = createGroupsLine(m_groups); // // Add annotations // for (MethodDoc md : methodDocs) { SourcePosition sp = md.position(); int line = sp.line() + 2; if (isTest(md)) { lines.add(line, " @Test" + groupsLine); } else if (isSetUp(md)) { ppp("ADDING NEW BEFORE AT " + line); lines.add(line, " @BeforeMethod" + groupsLine); } else if (isTearDown(md)) { lines.add(line, " @AfterMethod" + groupsLine); } } return lines; } private String createGroupsLine(String[] groups) { StringBuffer result = new StringBuffer(); if (groups != null) { result.append("(groups = {"); for (int i = 0; i < groups.length; i++) { if (i > 0) result.append(", "); result.append("\""+ groups[i] + "\""); } result.append("})"); } return result.toString(); } private List<String> insertJavadoc(File file, List<MethodDoc> methodDocs, List<String> lines) { for (int i = 0; i < methodDocs.size(); i++) { MethodDoc md = methodDocs.get(i); int insertLineNo; if (i + 1 < methodDocs.size()) { insertLineNo = findCommentLine(lines, md, methodDocs.get(i + 1) .position().line()); } else { insertLineNo = findCommentLine(lines, md, m_typelines.get(file) .intValue()); } int realInsert = insertLineNo == 0 ? md.position().line() - 1 : insertLineNo; if (insertLineNo == 0) { lines.add(realInsert, " */"); } if (isTest(md)) { lines.add(realInsert, " * @testng.test"); } else if (isSetUp(md)) { lines.add(realInsert, " * @testng.before-method"); } else if (isTearDown(md)) { lines.add(realInsert, " * @testng.after-method"); } if (insertLineNo == 0) { lines.add(realInsert, " /**"); } } return lines; } private File getPackageOutputDir(File outDir, String packageName) { if (packageName == null) packageName = ""; return new File(outDir, packageName.replace('.', File.separatorChar)); } private int findCommentLine(List<String> lines, MethodDoc md, int minLine) { for (int i = md.position().line() - 1; i > minLine; i--) { String line = lines.get(i); if (line.indexOf("*/") != -1 && line.indexOf("/**") == -1) { return i; } else if (line.indexOf("*/") != -1 && line.indexOf("/**") != -1) { // HINT /** and */ on same line: must split StringBuffer buf = new StringBuffer(line); int idx = buf.indexOf("*/"); buf.deleteCharAt(idx).deleteCharAt(idx); lines.set(i, buf.toString()); lines.add(i + 1, " */"); return i + 1; } } return 0; } /** * Convert a file into a list of its lines * * @throws IOException */ private List<String> fileToLines(File file) throws IOException { List<String> result = Lists.newArrayList(); BufferedReader br = new BufferedReader(new FileReader(file)); String line = br.readLine(); while (null != line) { result.add(line); line = br.readLine(); } return result; } static private void ppp(String s) { if (JUnitConverter.getLogLevel() >= 1) { System.out.println("[JUnitTestConverter]" + s); } } public String[] getClassNames() { return m_classNames.toArray(new String[m_classNames.size()]); } }